#ifdef _WIN32

#include "MouseListenerJNI.h"
#include <windows.h>
#include <stdio.h>

// 全局变量
HINSTANCE g_hInstance;
RECT g_rect = {0, 0, 0, 0};  // 初始化为零矩形
HHOOK g_hMouseHook = NULL;
static HANDLE g_messageThread = NULL;  // 消息循环线程句柄

static JavaVM *g_jvm = NULL;  // 更推荐保存JavaVM
static jobject g_obj = NULL;   // 保存全局引用

// 跟踪点击状态
static BOOL g_isMouseDown = FALSE;

// 添加限流相关变量
static DWORD g_lastScrollTime = 0;
const DWORD SCROLL_INTERVAL = 100; // 限制滚动间隔为100毫秒
void FilePrint(const char* message) {
    FILE* file = fopen("mouse_listener.log", "a");
    if (file) {
        fprintf(file, "%s\n", message);
        fclose(file);
    }
}
// 简单的限流函数
BOOL IsScrollAllowed() {
    DWORD currentTime = GetTickCount();
    if (currentTime - g_lastScrollTime > SCROLL_INTERVAL) {
        g_lastScrollTime = currentTime;
        return TRUE;
    }
    return FALSE;
}

// 调用Java回调方法
void CallJavaMouseClickCallback() {
    if (g_jvm != NULL && g_obj != NULL) {
        JNIEnv *local_env;
        // 因为是在独立的线程中运行钩子，所以需要附加此线程到jvm以获取jvm环境
        if ((*g_jvm)->AttachCurrentThread(g_jvm, (void**)&local_env, NULL) == JNI_OK) {
            jclass cls = (*local_env)->GetObjectClass(local_env, g_obj);
            jmethodID mid = (*local_env)->GetMethodID(local_env, cls, "mouseClickCallBack", "()V");
            if (mid != NULL) {
                (*local_env)->CallVoidMethod(local_env, g_obj, mid);
            }
        }
    }
}

/**
* @brief 鼠标底层钩子回调函数，在鼠标事件触发效果前调用，用于拦截和处理鼠标点击及滚轮事件。
*
* 该函数通过设置鼠标钩子来捕获全局鼠标事件。它会检查鼠标点击或滚轮操作是否发生在指定的窗口区域内，
* 如果在目标窗口内，则根据事件类型决定是否拦截或放行该事件。
*
* @param nCode 钩子代码，如果小于0则直接传递给下一个钩子；否则表示可以处理。
* @param wParam 消息标识符，如 WM_LBUTTONDOWN、WM_MOUSEWHEEL 等。
* @param lParam 指向 MSLLHOOKSTRUCT 结构的指针，包含鼠标事件的详细信息。
* @return 返回 1 表示拦截该事件；否则调用 CallNextHookEx 继续传递事件。
*/
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        // 只处理点击事件（左/右/中键按下）
        if (wParam == WM_LBUTTONDOWN ||
            wParam == WM_RBUTTONDOWN ||
            wParam == WM_MBUTTONDOWN) {

            MSLLHOOKSTRUCT *pMouseStruct = (MSLLHOOKSTRUCT *) lParam;
            POINT pt = pMouseStruct->pt;

            // 检查点击是否在窗口区域内
            if (PtInRect(&g_rect, pt)) {
                // 记录按下状态
                g_isMouseDown = TRUE;
                // 在窗口区域内则拦截点击
                return 1;
            }
        }
        // 鼠标左按键释放事件
        if (wParam == WM_LBUTTONUP) {
            MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
            POINT pt = pMouseStruct->pt;

            // PtInRect 函数确定指定的点是否位于指定的矩形内。
            if (PtInRect(&g_rect, pt) &&  g_isMouseDown) {
                g_isMouseDown = FALSE;
                FilePrint("click the area\n");
                // 发送点击事件给java
                // 调用Java回调方法
                CallJavaMouseClickCallback();

                return 1;
            } else {
                g_isMouseDown = FALSE;
            }
        }
        // 拦截滚轮事件
        if (wParam == WM_MOUSEWHEEL || wParam == WM_MOUSEHWHEEL) {
            MSLLHOOKSTRUCT *pMouseStruct = (MSLLHOOKSTRUCT *) lParam;
            POINT pt = pMouseStruct->pt;

            // 检查滚动事件是否在指定矩形范围内
            if (PtInRect(&g_rect, pt)) {
                // 使用限流函数控制滚动事件频率
                if (IsScrollAllowed()) {
                    FilePrint("Mouse wheel event detected\n");
                } else {
                    FilePrint("Scroll event blocked by rate limiter\n");
                    return 1;
                }
            } else {
                FilePrint("Mouse wheel event detected outside overlay\n");
            }
        }
    }
    // 放行此鼠标事件
    return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam);
}

/**
 * 设置监听区域
 */
JNIEXPORT void JNICALL
Java_MouseListenerJNI_SetListenRect(JNIEnv *env, jobject obj, jint left, jint top, jint right, jint bottom) {
    g_rect.left = (int) left;
    g_rect.top = (int) top;
    g_rect.right = (int) right;
    g_rect.bottom = (int) bottom;
    FilePrint("MouseListenerJNI: SetListenRect called");
    // 保存全局引用
    if (g_obj == NULL) {
        g_obj = (*env)->NewGlobalRef(env, obj);  // 创建全局引用
        if (g_obj == NULL) {
            // 日志输出或错误处理
            FilePrint("MouseListenerJNI: Failed to create global reference");
            return;
        }
    }
    // 获取JavaVM（只需要获取一次）
    if (g_jvm == NULL) {
        (*env)->GetJavaVM(env, &g_jvm);
        if ((*env)->GetJavaVM(env, &g_jvm) != JNI_OK) {
            FilePrint("MouseListenerJNI: Failed to get JavaVM");
            return;
        }
    }

}

/*
 * Class:     MouseListenerJNI
 * Method:    GetListenRect
 * Signature: ([I)V
 */
JNIEXPORT void JNICALL Java_MouseListenerJNI_GetListenRect(JNIEnv *env, jobject obj, jintArray rectArray) {
    // 检查数组是否有效
    if (rectArray == NULL) {
        return;
    }
    // 检查数组长度是否为4
    jsize length = (*env)->GetArrayLength(env, rectArray);
    if (length != 4) {
        return;
    }

    // 获取数组元素
    jint *rectElements = (*env)->GetIntArrayElements(env, rectArray, NULL);
    if (rectElements == NULL) {
        return;
    }

    // 设置数组元素
    rectElements[0] = g_rect.left;
    rectElements[1] = g_rect.top;
    rectElements[2] = g_rect.right;
    rectElements[3] = g_rect.bottom;
    // 释放数组元素
    (*env)->ReleaseIntArrayElements(env, rectArray, rectElements, 0);
}

// 消息循环线程过程
DWORD WINAPI HookMessageLoop(LPVOID lpParameter) {
    FilePrint("Message loop thread started\n");
    g_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, g_hInstance, 0);
    MSG msg;
    // 使用 GetMessage 替代 PeekMessage，可以在无消息时阻塞，避免空循环
    while (GetMessage(&msg, NULL, 0, 0)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // 在钩子线程退出前分离JVM，在钩子线程中分离，而不是Java主线程，在java主线程中调用DetachCurrentThread会导致java程序无法正常结束运行
    if (g_jvm != NULL) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
        FilePrint("Hook thread detached\n");
    }
    FilePrint("Message loop thread exited\n");
    return 0;
}

/**
 * 安装鼠标钩子，消息循环和鼠标钩子应该运行于同一个线程，因为消息循环时一个死循环，为了避免java调用此方法时阻塞，这里创建一个单独的线程来运行消息循环和安装钩子。
 */
JNIEXPORT jboolean JNICALL Java_MouseListenerJNI_InstallMouseHook
        (JNIEnv *env, jobject obj) {
    FilePrint("Installing mouse hook...\n");
    if (g_hMouseHook != NULL) {
        return JNI_TRUE; // 钩子已经安装
    }
    g_messageThread = CreateThread(NULL, 0, HookMessageLoop, NULL, 0, NULL);

    return (g_messageThread != NULL) ? JNI_TRUE : JNI_FALSE;
}

/**
 * 卸载鼠标钩子
 */
JNIEXPORT void JNICALL Java_MouseListenerJNI_UninstallMouseHook(JNIEnv *env, jobject obj) {
    // 先卸载钩子再退出线程
    if (g_hMouseHook) {
        // 卸载钩子
        UnhookWindowsHookEx(g_hMouseHook);
        g_hMouseHook = NULL;
    }
    if (g_messageThread) {
        // 发送WM_QUIT消息退出消息循环，GetMessage 收到WM_QUIT 会返回0从而退出while循环
        PostThreadMessage(GetThreadId(g_messageThread), WM_QUIT, 0, 0);

        // 消息发送是异步的，等待消息循环退出，线程自然结束（最多等待1秒）
        DWORD waitResult = WaitForSingleObject(g_messageThread, 1000);

        // 消息循环结束后，清理全局引用（消息循环结束后用到了全局引用来分离钩子线程，所以清除引用必须在这之后）
        if (g_obj != NULL && g_jvm != NULL) {
            JNIEnv* local_env;
            if ((*g_jvm)->GetEnv(g_jvm, (void**)&local_env, JNI_VERSION_1_6) == JNI_OK) {
                // 删除全局引用
                (*local_env)->DeleteGlobalRef(local_env, g_obj);
            }
            g_obj = NULL;
            g_jvm = NULL;
        }

        if (waitResult == WAIT_TIMEOUT) {
            // 如果线程仍未退出，则强制终止
            FilePrint("Thread did not exit gracefully, force terminating...\n");
            TerminateThread(g_messageThread, 0);
        }

        // 4. 关闭线程句柄
        CloseHandle(g_messageThread);
        g_messageThread = NULL;
    }
    FilePrint("Mouse hook uninstalled\n");
}
// 在DllMain中保存模块句柄
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH: {
            g_hInstance = hModule;
            break;
        }
        case DLL_PROCESS_DETACH: {
            // 确保所有资源被清理
            if (g_messageThread || g_hMouseHook) {
                Java_MouseListenerJNI_UninstallMouseHook(NULL, NULL);
            }
            break;
        }
    }
    return TRUE;
}
#endif
